package net.oschina.bilbodai.common.beanutil.bean.code;

import net.oschina.bilbodai.common.beanutil.bean.BeanDumpUtil;
import net.oschina.bilbodai.common.beanutil.bean.IBeanAccessor;
import net.oschina.bilbodai.common.beanutil.bean.core.PropertyDumpInfo;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Type;

import static net.oschina.bilbodai.common.beanutil.bean.BeanInfo.LOCAL_INDEX_COLLECT_ITERATOR;
import static net.oschina.bilbodai.common.beanutil.bean.BeanInfo.LOCAL_INDEX_COLLECT_ITERATOR_NEXT;
import static org.objectweb.asm.Opcodes.ACONST_NULL;
import static org.objectweb.asm.Opcodes.ALOAD;
import static org.objectweb.asm.Opcodes.ASTORE;
import static org.objectweb.asm.Opcodes.CHECKCAST;
import static org.objectweb.asm.Opcodes.DUP;
import static org.objectweb.asm.Opcodes.GOTO;
import static org.objectweb.asm.Opcodes.IFEQ;
import static org.objectweb.asm.Opcodes.IFNONNULL;
import static org.objectweb.asm.Opcodes.IFNULL;
import static org.objectweb.asm.Opcodes.INVOKEINTERFACE;
import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
import static org.objectweb.asm.Opcodes.INVOKESTATIC;
import static org.objectweb.asm.Opcodes.NEW;
import static org.objectweb.asm.Opcodes.POP;

/**
 * @author daimao  Date: 16/5/28 Time: 下午4:34
 * @version $Id$
 */
public class MapToCollectApplier extends DumpOverrideAwareApplier {

    public MapToCollectApplier(PropertyDumpInfo dumpInfo) {
        super(dumpInfo);
    }

    @Override
    protected void loadSrcCastPropertyOntoStack(MethodVisitor mv) {
        loadSrcCastPropertyOntoStack0(mv);
    }

    @Override
    protected void doApply(MethodVisitor mv) {
        loadSrcCastPropertyOntoStack(mv); //ALOAD 5
        Label l0 = new Label();
        mv.visitJumpInsn(IFNULL, l0); // if (srcMap != null) {

        loadTargetCastPropertyOntoStack(mv);// if ( targetList == null ) {
        Label l1 = new Label();
        mv.visitJumpInsn(IFNONNULL, l1);

        loadTargetCastOntoStack0(mv);// targetList = new XXList(); target.setXXList(targetList)
        mv.visitTypeInsn(NEW, dumpInfo.getCollectTypeImplInternalName());
        mv.visitInsn(DUP);
        mv.visitMethodInsn(INVOKESPECIAL, dumpInfo.getCollectTypeImplInternalName(), "<init>", "()V", false);
        invokeTargetPropertySetter0(mv);
        mv.visitLabel(l1); //} end if(targetList == null) }

        loadSrcCastPropertyOntoStack(mv); // Map.entrySet()
        mv.visitMethodInsn(INVOKEINTERFACE, "java/util/Map", "entrySet", "()Ljava/util/Set;", true);
        mv.visitMethodInsn(INVOKEINTERFACE, "java/util/Set", "iterator", "()Ljava/util/Iterator;", true);
        mv.visitVarInsn(ASTORE, LOCAL_INDEX_COLLECT_ITERATOR);

        Label l3 = new Label();// iterator.doestHasNext();
        Label l2 = new Label(); //while(iterator.hasNext){
        mv.visitLabel(l2);
        mv.visitVarInsn(ALOAD, LOCAL_INDEX_COLLECT_ITERATOR);
        mv.visitMethodInsn(INVOKEINTERFACE, "java/util/Iterator", "hasNext", "()Z", true);
        mv.visitJumpInsn(IFEQ, l3);

        mv.visitVarInsn(ALOAD, LOCAL_INDEX_COLLECT_ITERATOR);
        mv.visitMethodInsn(INVOKEINTERFACE, "java/util/Iterator", "next", "()Ljava/lang/Object;", true);
        mv.visitTypeInsn(CHECKCAST, "java/util/Map$Entry");
        mv.visitVarInsn(ASTORE, LOCAL_INDEX_COLLECT_ITERATOR_NEXT);//9

        loadTargetCastPropertyOntoStack(mv);

        if (dumpInfo.usingAttrPathQuery()) {
            dumpInfo.getAttrPathQueryInfo().preCheck(dumpInfo.getSrcComponentType());
            dumpInfo.getAttrPathQueryInfo().applyPath(mv);
        } else {
            mv.visitLdcInsn(Type.getType(dumpInfo.getSrcComponentType())); // create accessor
            mv.visitLdcInsn(Type.getType(dumpInfo.getTargetComponentType()));
            mv.visitLdcInsn(dumpInfo.getProfile());
            mv.visitMethodInsn(INVOKESTATIC, Type.getInternalName(BeanDumpUtil.class),
                    BeanDumpUtil.CREATE_ACCESSOR_METHOD.getName(),
                    Type.getMethodDescriptor(BeanDumpUtil.CREATE_ACCESSOR_METHOD), false);
        }
        mv.visitVarInsn(ALOAD, LOCAL_INDEX_COLLECT_ITERATOR_NEXT);
        mv.visitMethodInsn(INVOKEINTERFACE, "java/util/Map$Entry", "getValue", "()Ljava/lang/Object;", true); // entry.getValue()
        if (dumpInfo.usingAttrPathQuery()) {
            dumpInfo.getAttrPathQueryInfo().applyInvoke(mv);
        }else{
            mv.visitInsn(ACONST_NULL);
            mv.visitMethodInsn(INVOKEINTERFACE, Type.getInternalName(IBeanAccessor.class), BeanDumpUtil.PROPERTY_DUMP_METHOD.getName(), Type.getMethodDescriptor(BeanDumpUtil.PROPERTY_DUMP_METHOD), true);
        }
        mv.visitMethodInsn(INVOKEINTERFACE, "java/util/Collection", "add", "(Ljava/lang/Object;)Z", true);
        mv.visitInsn(POP);

        mv.visitJumpInsn(GOTO, l2); // }

        mv.visitLabel(l3); // end while(iterator.hasNext)
        applySortInsIfPossible(mv);

        mv.visitLabel(l0); // end if (srcMap != null) }

    }
}